//
//  SocketEngine.m
//  Socket.IO-Client
//
//  Created by alanwang.dev on 16/8/8.
//  Copyright © 2016年 com.alanwang.dev. All rights reserved.
//

#import "SocketEngine.h"
#import "SocketStringReader.h"


@implementation URLSDict
@end

@interface SocketEngine ()

@end

@implementation SocketEngine

@synthesize waitingForPoll;
@synthesize waitingForPost;
@synthesize postWait;
@synthesize invalidated;
@synthesize urlPolling;
@synthesize urlWebSocket;
@synthesize connectParams = _connectParams;
- (instancetype)initWithClient:(SocketIOClient<SocketEngineClient> *)client url:(NSURL *)url
{
    if (self = [super init]) {
        
        self.client = client;
        self.url = url;
        
        self.postWait = [[NSMutableArray alloc] init];
        self.waitingForPoll = NO;
        self.waitingForPost = NO;
        self.closed = NO;
        self.connected = NO;
        self.doubleEncodeUTF8 = YES;
        self.fastUpgrade = NO;
        self.forcePolling = NO;
        self.forceWebsockets = YES;
        self.invalidated = NO;
        self.polling = YES;
        self.probing = NO;
        self.sid = [[NSMutableString alloc] init];
        self.socketPath = [NSMutableString stringWithFormat:@"%@", @"/socket.io/"];
        self.urlPolling = [[NSURL alloc] init];
        self.urlWebSocket = [[NSURL alloc] init];
        self.logType = @"SocketEngine";
        self.pingInterval = 0.0;
        self.pongsMissed = 0;
        self.pongsMissedMax = 0;
        self.probeWait = [[NSMutableArray alloc] init];
        self.secure = NO;
        self.selfSigned = NO;
        self.voipEnabled = NO;
    }
    URLSDict *urls = [self createURLs];
    self.urlPolling = urls.firstURL;
    self.urlWebSocket = urls.secondURL;
    
    return self;
}

- (instancetype)initWithClient:(id)client url:(NSURL *)url options:(NSDictionary *)options {
  if (self = [super init]) {
    
    self.client = client;
    self.url = url;
    
    self.postWait = [[NSMutableArray alloc] init];
    self.waitingForPoll = NO;
    self.waitingForPost = NO;
    self.closed = NO;
    self.connected = NO;
    self.doubleEncodeUTF8 = YES;
    self.fastUpgrade = NO;
    self.forcePolling = NO;
    self.forceWebsockets = YES;
    self.invalidated = NO;
    self.polling = YES;
    self.probing = NO;
    self.sid = [[NSMutableString alloc] init];
    self.socketPath = [NSMutableString stringWithFormat:@"%@", @"/socket.io/"];
    self.urlPolling = [[NSURL alloc] init];
    self.urlWebSocket = [[NSURL alloc] init];
    self.logType = @"SocketEngine";
    self.pingInterval = 0.0;
    self.pongsMissed = 0;
    self.pongsMissedMax = 0;
    self.probeWait = [[NSMutableArray alloc] init];
    self.secure = NO;
    self.selfSigned = NO;
    self.voipEnabled = NO;
    _connectParams = options[@"connectParams"];
  }
  URLSDict *urls = [self createURLs];
  self.urlPolling = urls.firstURL;
  self.urlWebSocket = urls.secondURL;
  
  return self;

}
- (void)checkAndHandleEngineError:(NSString *)msg
{
    NSData *stringData = [msg dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
    if (!stringData)
        return;
    
    @try {
        NSError *error;
        id JSONData = [NSJSONSerialization JSONObjectWithData:stringData options:NSJSONReadingMutableContainers error:&error];
        if ([JSONData isKindOfClass:[NSDictionary class]]) {
            NSDictionary *dict = JSONData;
            id error = dict[@"message"];
            if (![error isKindOfClass:[NSString class]])
                return;
            /*
             0: Unknown transport
             1: Unknown sid
             2: Bad handshake request
             3: Bad request
             */
            [self didError:(NSString *)error];
        }
        
    } @catch (NSException *exception) {
        [self didError:[NSString stringWithFormat:@"Got unknown error from server %@", msg]];
    }
}

- (BOOL)checkIfMessageIsBase64Binary:(NSString *)message{
    if ([message hasPrefix:@"b4"]){
        NSString *noPrefix = [message substringFromIndex:2];
        NSData *data = [[NSData alloc] initWithBase64EncodedString:noPrefix options:NSDataBase64DecodingIgnoreUnknownCharacters];
        if (!data && !self.client)
            [self.client parseEngineBinaryData:data];
        return YES;
    } else {
        return NO;
    }
}

- (void)connect{
    if (self.connected) {
        [self disconnect:@"reconnet"];
    }
    [self resetEngine];
    
    if (self.forceWebsockets) {
        self.polling = NO;
        self.websocket = YES;
        [self createWebsocketAndConnect];
        return;
    }
    
    NSMutableURLRequest *reqPolling = [[NSMutableURLRequest alloc] initWithURL:self.urlPolling];
    
    if (!self.cookies) {
        NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:self.cookies];
        reqPolling.allHTTPHeaderFields = headers;
    }
    
    if (!self.extraHeaders) {
        for (NSString *key in [self.extraHeaders allKeys]) {
            [reqPolling setValue:self.extraHeaders[key] forHTTPHeaderField:key];
        }
    }
    
    dispatch_async(self.emitQueue, ^{[self doLongPoll:reqPolling];});
}

- (URLSDict *)createURLs{
    if (!self.client) {
        URLSDict *urls = [[URLSDict alloc] init];
        urls.firstURL = [[NSURL alloc] init];
        urls.secondURL = [[NSURL alloc] init];
        return urls;
    }
    
    NSURLComponents *_urlPolling = [[NSURLComponents alloc] initWithString:self.url.absoluteString];
    NSURLComponents *_urlWebSocket = [[NSURLComponents alloc] initWithString:self.url.absoluteString];
    NSString *queryString = [[NSString alloc] init];
    _urlWebSocket.path = self.socketPath;
    _urlPolling.path= self.socketPath;
    
    if (self.secure) {
        _urlPolling.scheme = @"https";
        _urlWebSocket.scheme = @"wss";
    } else {
        _urlPolling.scheme = @"http";
        _urlWebSocket.scheme = @"ws";
    }
    
    if (self.connectParams) {
        NSCharacterSet *allowedURLCharacterSet = [[NSCharacterSet characterSetWithCharactersInString:@"!*'();:@&=+$,/?%#[]\" {}"] invertedSet];
        for (NSString *key in self.connectParams) {
            NSString *keyEsc = [key stringByAddingPercentEncodingWithAllowedCharacters:allowedURLCharacterSet];
            NSString *valueString = [NSString stringWithFormat:@"%@", self.connectParams[key]];
            NSString *valueEsc = [valueString stringByAddingPercentEncodingWithAllowedCharacters:allowedURLCharacterSet];
            
            queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&%@=%@", keyEsc, valueEsc]];
        }

        
    }
  _urlWebSocket.percentEncodedQuery = [@"transport=websocket" stringByAppendingString:queryString];
  _urlPolling.percentEncodedQuery = [@"transport=polling&b64=1" stringByAppendingString:queryString];
    URLSDict *urls = [[URLSDict alloc] init];
    urls.firstURL = _urlPolling.URL;
    urls.secondURL = _urlWebSocket.URL;
    
    return urls;
}

- (void)createWebsocketAndConnect{
    self.ws = [[WebSocket alloc] initWithURL:self.urlWebSocketWithSid protocols:nil];
    
    if (self.cookies) {
        NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:self.cookies];
        for (NSString *key in headers) {
            if (self.ws) {
                self.ws.headers[key] = headers[key];
            }
        }
    }
    
    if (self.extraHeaders) {
        for (NSString *headerName in self.extraHeaders) {
            if (self.ws) {
                self.ws.headers[headerName] = self.extraHeaders[headerName];
            }
        }
    }
    
    if (self.ws) {
        self.ws.queue = self.handleQueue;
        self.ws.voipEnabled = self.voipEnabled;
        self.ws.deleate = self;
        self.ws.selfSignedSSL = self.selfSigned;
        [self.ws connect];
    }
}

- (void)didError:(NSString *)error{
    if (self.client) {
        [self.client engineDidError:error];
        [self disconnect:error];
    }
}

- (void)disconnect:(NSString *)reason {
    if (!self.connected) {
        return;
    }
    if (self.closed && self.client) {
        [self.client engineDidClose:reason];
        return;
    }
    
    if (self.websocket) {
        [self sendWebSocketMessage:@"" type:PacketType_Close datas:nil];
        
        self.sid = [[NSMutableString alloc] init];
        self.closed = YES;
        self.invalidated = YES;
        self.connected = NO;
        
        if (self.ws) {
            [self.ws disconnect:-1];
        }
        [self stopPolling];
        if (self.client) {
            [self.client engineDidClose:reason];
        }
    } else {
        dispatch_sync(self.emitQueue, ^{
            NSString *rawValue = [NSString stringWithFormat:@"%d", PacketType_Close];
            [self.postWait addObject:rawValue];
            NSURLRequest *req = [self createRequestForPostWithPostWait];
            [self doRequest:req withCallback:^(NSData *data, NSURLResponse *res, NSError *err) {
                self.sid = [[NSMutableString alloc] init];
                self.closed = YES;
                self.invalidated = YES;
                self.connected = NO;
                
                if (self.ws) {
                    [self.ws disconnect:0];
                }
                [self stopPolling];
                if (self.client) {
                    [self.client engineDidClose:reason];
                }
            }];
        });
    }
}

///////////////////////////////////////////////
- (void)doFastUpgrade
{
    [self sendWebSocketMessage:@"" type:PacketType_Upgrade datas:nil];
    self.websocket = YES;
    self.polling = NO;
    self.fastUpgrade = NO;
    self.probing = NO;
    [self flushProbeWait];
}

- (void)flushProbeWait
{
    dispatch_async(self.emitQueue, ^{
        for (Probe *waiter in self.probeWait) {
            [self write:waiter.msg type:waiter.type Data:waiter.data];
        }
        
        [self.probeWait removeAllObjects];
        
        if (self.postWait.count != 0) {
            [self flushWaitingForPostToWebSocket];
        }
    });
}

// We had packets waiting for send when we upgraded
// Send them raw
- (void)flushWaitingForPostToWebSocket
{
    if (!self.ws)
        return;
    for (NSString *msg in self.postWait) {
        [self.ws wirteString:[self fixDoubleUTF8:msg] completion:nil];
    }
    [self.probeWait removeAllObjects];
}

- (void)handleClose:(NSString *)reason
{
    if (self.client) {
        [self.client engineDidClose:reason];
    }
}

- (void)handleMessage:(NSString *)message
{
    if (self.client) {
        [self.client parseEngineMessage:message];
    }
}

- (void)handleNOOP
{
    [self doPoll];
}

- (void)handleOpen:(NSString *)openData
{
    NSData *mesData = [openData dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
    @try {
        id jsonData = [NSJSONSerialization JSONObjectWithData:mesData options:NSJSONReadingAllowFragments error:nil];
        NSDictionary *json = nil;
        if ([jsonData isKindOfClass:[NSDictionary class]]) {
            json = jsonData;
        }
        if (json && [json[@"sid"] isKindOfClass:[NSString class]]) {
            NSString *sid = json[@"sid"];
            BOOL upgradeWs;
            self.sid = [NSMutableString stringWithString:sid];
            self.connected = YES;
            
            if ([json[@"upgrades"] isKindOfClass:[NSArray class]]) {
                NSArray *upgrades = json[@"upgrades"];
                upgradeWs = [upgrades containsObject:@"websocket"];
            } else {
                upgradeWs = NO;
            }
            
            if ([json[@"pingInterval"] isKindOfClass:[NSNumber class]] && [json[@"pingTimeout"] isKindOfClass:[NSNumber class]]) {
                CGFloat pingInterval = [json[@"pingInterval"] floatValue];
                CGFloat pingTimeout = [json[@"pingTimeout"] floatValue];
                self.pingInterval = pingInterval / 1000.0;
                self.pingTimeout = pingTimeout / 1000.0;
            }
            
            if (!self.forcePolling && !self.forceWebsockets && upgradeWs) {
                [self createWebsocketAndConnect];
            }
            
            [self sendPing];
            
            if (!self.forceWebsockets) {
                [self doPoll];
            }
            
            if (!self.client) {
                [self.client engineDidOpen:@"Connect"];
            }
        }
    } @catch (NSException *exception) {
        [self didError:@"Error parsing open packet"];
    }
}

- (void)handlePong:(NSString *)pongMessage
{
    self.pongsMissed = 0;
    // We should upgrade
    if ([pongMessage isEqualToString:@"3probe"]) {
        [self upgradeTransport];
    }
}

- (void)parseEngineData:(NSData *)data
{
    if (self.client) {
        [self.client parseEngineBinaryData:[data subdataWithRange:NSMakeRange(1, data.length - 1)]];
    }
}

- (void)parseEngineMessage:(NSString *)message fromPolling:(BOOL)fromPolling
{
    SocketStringReader *reader = [[SocketStringReader alloc] initWithMessage:message];
    NSString *fixedString;
    
    
    char ch = [reader.currentCharacter characterAtIndex:0];
    if (!(ch >= '0' && ch <= '6')) {
        if ([self checkIfMessageIsBase64Binary:message]) {
            [self checkAndHandleEngineError:message];
        }
        return;
    }
    
    SocketEnginePacketType type = (SocketEnginePacketType)[reader.currentCharacter integerValue];
    
    if (fromPolling && type != PacketType_Noop && self.doubleEncodeUTF8) {
        fixedString = [self fixDoubleUTF8:message];
    } else {
        fixedString = message;
    }
    
    switch (type) {
        case PacketType_Message:
            [self handleMessage:[fixedString substringFromIndex:1]];
            break;
        case PacketType_Noop:
            [self handleNOOP];
            break;
        case PacketType_Pong:
            [self handlePong:fixedString];
            break;
        case PacketType_Open:
            [self handleOpen:[fixedString substringFromIndex:1]];
            break;
        case PacketType_Close:
            [self handleClose:fixedString];
            break;
        default:
            break;
    }
}

// Puts the engine back in its default state
- (void)resetEngine{
    self.closed = NO;
    self.connected = NO;
    self.fastUpgrade = NO;
    self.polling = YES;
    self.invalidated = NO;
    self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self.sessionDelegate delegateQueue:[[NSOperationQueue alloc] init]];
    self.sid = [[NSMutableString alloc] init];
    self.waitingForPoll = NO;
    self.waitingForPost = NO;
    self.websocket = NO;
}

- (void)sendPing
{
  NSLog(@"send Ping");
    if (!self.connected) {
        return;
    }
    //Server is not responding
    if (self.pongsMissed > self.pongsMissedMax && self.client) {
        [self.client engineDidClose:@"Ping timeout"];
        return;
    }
    
    if (self.pingInterval != 0) {
        self.pongsMissed += 1;
        [self write:@"" type:PacketType_Ping Data:nil];
        
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.pingInterval * NSEC_PER_SEC));
        __weak typeof(self) weakself = self;
        dispatch_after(time, dispatch_get_main_queue(), ^{
            __strong typeof(weakself) this = weakself;
            if (this) {
                [this sendPing];
            }
        });
    }
}

// Moves from long-polling to websockets
- (void)upgradeTransport
{
    if (self.ws && self.ws.isConnected) {
        self.fastUpgrade = YES;
        [self sendPollMessage:@"" withType:PacketType_Noop withData:nil];
        // After this point, we should not send anymore polling messages
    }
}

- (void)write:(NSString *)msg type:(SocketEnginePacketType)type Data:(NSArray<NSData *> *)data
{
    dispatch_async(self.emitQueue, ^{
        if (!self.connected)
            return;
        if (self.websocket) {
            [self sendWebSocketMessage:msg type:type datas:data];
        } else if (!self.probing) {
            [self sendPollMessage:msg withType:type withData:data];
        } else {
            Probe *probe = [[Probe alloc] init];
            probe.msg = msg;
            probe.type = type;
            probe.data = data;
            [self.probeWait addObject:probe];
        }
    });
}

- (void)websocketDidConnect:(WebSocket *)socket
{
    if (!self.forceWebsockets) {
        self.probing = YES;
        [self probeWebSocket];
    } else {
        self.connected = YES;
        self.probing = NO;
        self.polling = NO;
    }
}

- (void)websocketDidDisconnect:(WebSocket *)socket error:(NSError *)error
{
    self.probing = NO;
    
    if (self.closed) {
        if (self.client){
            [self.client engineDidClose:@"Disconnect"];
        }
        return;
    }
    
    if (self.websocket) {
        self.connected = NO;
        self.websocket = NO;
        
        NSString *reason;
        if (error) {
            reason = error.localizedDescription;
        } else {
            reason = @"Socket Disconnected";
        }
        
        if (error) {
            [self didError:reason];
        }
        
        if (self.client) {
            [self.client engineDidClose:reason];
        }
    } else {
        [self flushProbeWait];
    }
}

#pragma mark - lazy

- (void)setConnectParams:(NSDictionary *)connectParams{
    _connectParams = connectParams;
    URLSDict *urls = [self createURLs];
    self.urlPolling = urls.firstURL;
    self.urlWebSocket = urls.secondURL;
}

- (void)setPingTimeout:(CGFloat)pingTimeout{
    _pingTimeout = pingTimeout;
    _pongsMissedMax = pingTimeout / (_pingInterval==0 ? 25 : _pingInterval);
}

- (void)dealloc{
    self.closed = YES;
    [self stopPolling];
}


//SocketEnginePollableProtocol协议扩展实现方法
#pragma mark - SocketEnginePollableProtocol
- (void)addHeaders:(NSMutableURLRequest *)req
{
    if (self.cookies) {
        NSDictionary *headers = [NSHTTPCookie requestHeaderFieldsWithCookies:self.cookies];
        req.allHTTPHeaderFields = headers;
    }
    
    if (self.extraHeaders) {
        [self.extraHeaders enumerateKeysAndObjectsUsingBlock:^(id headerName, id value, BOOL * _Nonnull stop) {
            [req setValue:value forHTTPHeaderField:headerName];
        }];
    }
}

- (NSURLRequest *)createRequestForPostWithPostWait
{
    NSMutableString *postStr = [NSMutableString string];
    
    for (NSString *packet in self.postWait) {
        NSInteger len = [packet length];
        
        [postStr appendFormat:@"%ld:%@",(long)len,packet];
    }
    
    // TODO: DefaultSocketLogger
    [self.postWait removeAllObjects];
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:self.urlPollingWithSid];
    
    [self addHeaders:req];
    
    req.HTTPMethod = @"POST";
    [req setValue:@"text/plain; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
    
    NSData *postData = [postStr dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
    
    req.HTTPBody = postData;
    [req setValue:[NSString stringWithFormat:@"%ld",postData.length] forHTTPHeaderField:@"Content-Length"];
    
    return req;
}

- (void)doPoll
{
    if (self.websocket || waitingForPoll || !self.connected || self.closed) {
        return;
    }
    
    waitingForPoll = YES;
    
    NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:self.urlPollingWithSid];
    
    [self addHeaders:req];
    [self doLongPoll:req];
}

- (void)doRequest:(NSURLRequest *)req withCallback:(void (^)(NSData *, NSURLResponse *, NSError *))callback
{
    if (self.polling || self.closed || invalidated || self.fastUpgrade) {
        // TODO: DefaultSocketLogger
        return;
    }
    
    // TODO: DefaultSocketLogger
    
    [[self.session dataTaskWithRequest:req completionHandler:callback] resume];
}

- (void)doLongPoll:(NSURLRequest *)req
{
    __weak typeof(self) this = self;
    [self doRequest:req withCallback:^(NSData *data, NSURLResponse *res, NSError *err) {
        if (!this.polling) {
            return;
        }
        
        if (err != nil || data == nil) {
            // TODO: DefaultSocketLogger
            
            if (this.polling) {
                [this didError:[err localizedDescription] ? [err localizedDescription] : @"Error"];
            }
            
            return;
        }
        
        // TODO: DefaultSocketLogger
        
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        dispatch_async(this.parseQueue, ^{
            [this parsePollingMessage:str];
        });
        
        this.waitingForPoll = NO;
        
        if (this.fastUpgrade) {
            [this doFastUpgrade];
        }else if (!this.closed && this.polling){
            [this doPoll];
        }
    }];
}

- (void)flushWaitingForPost
{
    if (postWait.count == 0 || !self.connected) {
        return;
    }else if (self.websocket){
        [self flushWaitingForPostToWebSocket];
        return;
    }
    
    NSURLRequest *req = [self createRequestForPostWithPostWait];
    
    waitingForPost = YES;
    
    // TODO: DefaultSocketLogger
    
    __weak typeof(self) this = self;
    [self doRequest:req withCallback:^(NSData *data, NSURLResponse *res, NSError *err) {
        if (err != nil) {
            // TODO: DefaultSocketLogger
            
            if (this.polling) {
                [this didError:[err localizedDescription] ? [err localizedDescription] : @"Error"];
            }
            
            return ;
        }
        
        this.waitingForPost = NO;
        
        dispatch_async(this.emitQueue, ^{
            if (!this.fastUpgrade) {
                [this flushWaitingForPost];
                [this doPoll];
            }
        });
    }];
    
}

- (void) parsePollingMessage:(NSString *)str
{
    if (str.length == 1) {
        return;
    }
    
    SocketStringReader *reader = [[SocketStringReader alloc] initWithMessage:str];
    
    while ([reader hasNext]) {
        NSString *len_str = [reader readUntilStringOccurence:@":"];
        if (len_str) {
            NSInteger n = [str integerValue];
            NSString *str = [reader read:n];
            
            __weak typeof(self) this = self;
            dispatch_async(self.handleQueue, ^{
                [this parseEngineMessage:str fromPolling:YES];
            });
        }else{
            __weak typeof(self) this = self;
            dispatch_async(self.handleQueue, ^{
                [this parseEngineMessage:str fromPolling:YES];
            });
            break;
        }
    }
}

/// Send polling message.
/// Only call on emitQueue
- (void)sendPollMessage:(NSString *)message withType:(SocketEnginePacketType)type withData:(NSArray<NSData *> *)datas
{
    // TODO: DefaultSocketLogger
    NSString *fixedMessage;
    
    if (self.doubleEncodeUTF8) {
        fixedMessage = [self doubleEncodeUTF8:message];
    }else{
        fixedMessage = message;
    }
    
    NSString *strMsg = [NSString stringWithFormat:@"%d%@",type,fixedMessage];
    
    [postWait addObject:strMsg];
    
    for (NSData *data in datas) {
        // TODO: 此处可能存在问题
        Either *either = [self createBinaryDataForSend:data];
        if (either.Right && [either.Right isKindOfClass:[NSString class]]) {
            [postWait addObject:either.Right];
        }
    }
    
    if (!self.waitingForPost) {
        [self flushWaitingForPost];
    }
}

- (void)stopPolling
{
    self.waitingForPoll = NO;
    self.waitingForPost = NO;
    [self.session finishTasksAndInvalidate];
}

//SocketEngineWebSocketDelegate协议扩展实现方法
#pragma mark - SocketEngineWebSocketDelegate
- (void)probeWebSocket{
    if (self.ws.isConnected) {
        [self sendWebSocketMessage:@"probe" type:PacketType_Ping datas:nil];
    }
}

/// Send message on WebSockets
/// Only call on emitQueue
- (void)sendWebSocketMessage:(NSString *)str type:(SocketEnginePacketType)type datas:(NSMutableArray<NSData *> *)datas{
    // TODO: DefaultSocketLogger
    
    // TODO: 此处的rawValue 和hashValue 可能需要调整
    [self.ws wirteString:[NSString stringWithFormat:@"%d%@",type,str] completion:nil];
    
    for (NSData *data in datas) {
        // TODO: 此处的传值类型存在问题
        Either *either = [self createBinaryDataForSend:data];
        if (either.Left && [either.Left isKindOfClass:[NSData class]]) {
            [self.ws writeData:either.Left completion:nil];
        }
    }
}

- (void)websocketDidReceiveMessage:(WebSocket *)socket text:(NSString *)text{
    [self parseEngineMessage:text fromPolling:NO];
}

- (void)websocketDidReceiveData:(WebSocket *)socket data:(NSData *)data{
    [self parseEngineData:data];
}

@end
